/**
 * @file lv_gpu_v2d_blend.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_gpu_v2d.h"
#include "../lv_draw/lv_img_buf.h"
#include "../lv_misc/lv_gc.h"

#if LV_USE_GPU_V2D
/**********************
 *   GLOBAL FUNCTIONS
 **********************/

#if LV_USE_GPU_V2D_SOFTWARE_SIMULATOR
extern lv_disp_t * _lv_refr_get_disp_refreshing(void);
extern LV_ATTRIBUTE_FAST_MEM void ext_map_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
        const lv_area_t * draw_area,
        const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
        const lv_opa_t * mask, lv_draw_mask_res_t mask_res);


extern void ext_map_blended(const lv_area_t * disp_area, lv_color_t * disp_buf,  const lv_area_t * draw_area,
                            const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
                            const lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_blend_mode_t mode);

/*
* fg image is BGR5658, use v2d transfer interface, divide all pixel's alpha to mask buf(format A8), and BRG565 map2_buf
* if top layer has more mask layer, the pixel's mask_buf will be blend to all other masks. use v2d blend(fg A8, bg A8, output A8)
* so when invoke it, fg is BRG565, no alpha, and has mask buf
*/
/*
* fg_buf is BGR, no pixel alpha
* fg alpha from mask_buf, if has overall opa. premult factor is overall opa.
* bg_bub is BRG, blend BG and FG with blend mode.
* blend mode support alpha blend mode(normal mode) and add blend mode.
*/
void lv_gpu_v2d_blend_fg_no_alpha_has_mask(const lv_area_t * bg_area, lv_color_t * bg_buf,
        const lv_area_t * blend_area,
        const lv_area_t * fg_area, const uint8_t * fg_buf, lv_opa_t opa,
        const lv_area_t * mask_area, const lv_opa_t * mask_buf,
        lv_draw_mask_res_t mask_res,
        lv_blend_mode_t blend_mode)
{
    /* software simulate v2d */
    printf("[SW V2D]%s\n", __func__);

    lv_area_t draw_area;
    draw_area.x1 = blend_area->x1 - bg_area->x1;
    draw_area.y1 = blend_area->y1 - bg_area->y1;
    draw_area.x2 = blend_area->x2 - bg_area->x1;
    draw_area.y2 = blend_area->y2 - bg_area->y1;

    //bg
    lv_color_t * disp_buf = (lv_color_t *)bg_buf;
    const lv_area_t * disp_area = bg_area;
    //fg
    const lv_color_t * map_buf = (const lv_color_t *)fg_buf;
    const lv_area_t * map_area = fg_area;

    if(LV_BLEND_MODE_NORMAL == blend_mode) {
        ext_map_normal(disp_area, disp_buf, &draw_area, map_area, map_buf, opa, mask_buf, mask_res);
    } else {
        ext_map_blended(disp_area, disp_buf, &draw_area, map_area, map_buf, opa, mask_buf, mask_res, blend_mode);
    }

    return;
}


/*
 * bg BGR, fg BGRA:
 * mix alpha from pixel of fg, also support overall opa works as premix factor
 *
 * Support alpha preprocess, a v2d register for factor for premix,
 * eg: if the premix factor is overall opa, alpha selection is ALPHA_FROM_MASK_BUFFER,
 * then source_alpha = (premix factor * px_opa from mask) >> 8;
 *
 * bg_B = fg_B * source_alpha + bg_B *(1 - source_alpha);
 * bg_G = fg_G * source_alpha + bg_G *(1 - source_alpha);
 * bg_R = fg_R * source_alpha + bg_R *(1 - source_alpha);
 * bg_A = 255 or no bf_A; which will not transfer to LCD(BGA565)
 *
 */
void lv_gpu_v2d_blend_fg_has_alpha_no_mask(const lv_area_t * bg_area, lv_color_t * bg_buf,
        const lv_area_t * clip_area,
        const lv_area_t * fg_area, const uint8_t * fg_buf, lv_opa_t opa,
        lv_blend_mode_t blend_mode)
{
    /* software simulate v2d */
    printf("[SW V2D]%s\n", __func__);
    lv_disp_t * disp    = _lv_refr_get_disp_refreshing();

    //bg
    const lv_area_t * disp_area = bg_area;
    //fg
    const uint8_t * map_buf = fg_buf;
    const lv_area_t * map_area = fg_area;


    lv_area_t draw_area;
    /* Now `draw_area` has absolute coordinates.
     * Make it relative to `disp_area` to simplify draw to `disp_buf`*/
    draw_area.x1 = clip_area->x1 - disp_area->x1;
    draw_area.y1 = clip_area->y1 - disp_area->y1;
    draw_area.x2 = clip_area->x2 - disp_area->x1;
    draw_area.y2 = clip_area->y2 - disp_area->y1;

    bool alpha_byte = 1;

    /*The pixel size in byte is different if an alpha byte is added too*/
    uint8_t px_size_byte = alpha_byte ? LV_IMG_PX_SIZE_ALPHA_BYTE : sizeof(lv_color_t);

    /*Go to the first displayed pixel of the map*/
    int32_t map_w = lv_area_get_width(map_area);
    const uint8_t * map_buf_tmp = map_buf;
    map_buf_tmp += map_w * (draw_area.y1 - (map_area->y1 - disp_area->y1)) * px_size_byte;
    map_buf_tmp += (draw_area.x1 - (map_area->x1 - disp_area->x1)) * px_size_byte;

    uint32_t px_i = 0;

    const uint8_t * map_px;

    lv_area_t blend_area_tmp;
    blend_area_tmp.x1 = draw_area.x1 + disp_area->x1;
    blend_area_tmp.x2 = blend_area_tmp.x1 + lv_area_get_width(&draw_area) - 1;
    blend_area_tmp.y1 = disp_area->y1 + draw_area.y1;
    blend_area_tmp.y2 = blend_area_tmp.y1;

    lv_coord_t draw_area_h = lv_area_get_height(&draw_area);
    lv_coord_t draw_area_w = lv_area_get_width(&draw_area);

    uint32_t hor_res = (uint32_t) lv_disp_get_hor_res(disp);

    /* Build the image and a mask whold map */
    //uint32_t mask_buf_size = lv_area_get_size(&draw_area);
    /* Build the image and a mask line-by-line */
    uint32_t mask_buf_size = lv_area_get_size(&draw_area) > (uint32_t) hor_res ? hor_res : lv_area_get_size(&draw_area);
    lv_color_t * map2 = _lv_mem_buf_get(mask_buf_size * sizeof(lv_color_t));
    lv_opa_t * mask_buf = _lv_mem_buf_get(mask_buf_size);

    int32_t x;
    int32_t y;
    for(y = 0; y < draw_area_h; y++) {
        map_px = map_buf_tmp;
        for(x = 0; x < draw_area_w; x++, map_px += px_size_byte, px_i++) {
            lv_opa_t px_opa = map_px[LV_IMG_PX_SIZE_ALPHA_BYTE - 1];
            mask_buf[px_i] = px_opa;
            if(px_opa) {
#if LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1
                map2[px_i].full =  map_px[0];
#elif LV_COLOR_DEPTH == 16
                map2[px_i].full =  map_px[0] + (map_px[1] << 8);
#elif LV_COLOR_DEPTH == 32
                map2[px_i].full =  *((uint32_t *)map_px);
#endif
            }
#if LV_COLOR_DEPTH == 32
            map2[px_i].ch.alpha = 0xFF;
#endif
        }

        map_buf_tmp += map_w * px_size_byte;
        if(px_i + lv_area_get_width(&draw_area) < mask_buf_size) {
            blend_area_tmp.y2 ++;
        } else {
            //printf("[SW v2d] %s invoke _lv_blend_map, blend_area_tmp.y1=%d, blend_area_tmp.y2=%d, px_i=%d\n", __func__, blend_area_tmp.y1, blend_area_tmp.y2, px_i);
            _lv_blend_map(clip_area, &blend_area_tmp, map2, mask_buf, LV_DRAW_MASK_RES_CHANGED, opa, blend_mode);

            blend_area_tmp.y1 = blend_area_tmp.y2 + 1;
            blend_area_tmp.y2 = blend_area_tmp.y1;

            px_i = 0;
        }
    }
    /*Flush the last part*/
    if(blend_area_tmp.y1 != blend_area_tmp.y2) {
        //printf("-----test 258, blend_area_tmp.y1=%d, blend_area_tmp.y2=%d\n", blend_area_tmp.y1, blend_area_tmp.y2);
        blend_area_tmp.y2--;

        _lv_blend_map(clip_area, &blend_area_tmp, map2, mask_buf, LV_DRAW_MASK_RES_CHANGED, opa, blend_mode);
    }

    _lv_mem_buf_release(mask_buf);
    _lv_mem_buf_release(map2);
}
#endif /* LV_USE_GPU_V2D_SOFTWARE_SIMULATOR */

/**********************************************************
 * V2D *
 **********************************************************/
int v2d_blend(V2D_BLEND_T v2d_blend_info)
{
    (void)v2d_blend_info;

    //v2d set registers
    return 0;
}

int v2d_crop_and_transfer(V2D_SRC_SURFACE * src_surface, V2D_SRC_SURFACE * dst_surface)
{
    (void)src_surface;
    (void)dst_surface;
    //v2d set registers

    return 0;
}

int v2d_fill(V2D_BLEND_T * v2d_blend_info, uint32_t fg_recolor)
{
    (void)v2d_blend_info;
    (void)fg_recolor;
    /*fg_recolor_opa*/

    //v2d set registers
    return 0;
}

void lv_gpu_get_crop_area(const lv_area_t * big_area, const lv_area_t * small_area, V2D_AREA * crop_area)
{
    crop_area->x = small_area->x1 - big_area->x1;
    crop_area->y = small_area->y1 - big_area->y1;
    crop_area->w = small_area->x2 - small_area->x1;
    crop_area->h = small_area->y2 - small_area->y1;
}

static uint32_t lv_gpu_init_pitch(LV_V2D_IMAGE_FORMAT format, uint32_t width_pixels)
{
    uint32_t pitch = 0;

    /* support mask A8 blend mask A8 , bg is mask, pixel alpha */
    if(A8 == format) {
        pitch = width_pixels * 1;
    } else if(RGB565 == format) {
        pitch = width_pixels * 2;
    } else if(RGBA5658 == format) {
        pitch = width_pixels * 3;
    } else if(RGBA8888 == format) {
        pitch = width_pixels * 4;
    } else {
        printf("Please change format firstly!");
        return 0;
    }

    return pitch;
}

/* bg_area, blend_area, fg_area, mask_ared all is absolute position */
static int lv_gpu_blend_init(const lv_area_t * bg_area, uint8_t * bg_buf, LV_V2D_IMAGE_FORMAT bg_format,
                             const lv_area_t * blend_area,
                             const lv_area_t * fg_area, const uint8_t * fg_buf, lv_opa_t opa, LV_V2D_IMAGE_FORMAT fg_format,
                             const lv_area_t * mask_area, const lv_opa_t * mask_buf,
                             lv_draw_mask_res_t mask_res,
                             lv_blend_mode_t blend_mode,
                             V2D_BLEND_T * v2d_blend_info)
{
    V2D_SRC_SURFACE * bg_surface = &v2d_blend_info->bg_surface;
    V2D_SRC_SURFACE * fg_surface = &v2d_blend_info->fg_surface;
    V2D_SRC_SURFACE * mask_surface = &v2d_blend_info->mask_surface;
    V2D_BLEND_OUT_SURFACE * out_surface = &v2d_blend_info->out_surface;

    if(mask_res == LV_DRAW_MASK_RES_TRANSP) {
        return 0;
    }

    //bg
    bg_surface->buf = (void *)bg_buf;
    bg_surface->w = bg_area->x2 - bg_area->x1;
    bg_surface->h = bg_area->y2 - bg_area->y1;

    bg_surface->pitch = lv_gpu_init_pitch(bg_format, bg_surface->w);
    if(0 == bg_surface->pitch) {
        printf("[ERR] unsupported bg format =%d\n", bg_format);
        return -1;
    }

    /* Make bg crop area relative to `bg_area` to simplify draw to `bg_buf`*/
    lv_gpu_get_crop_area(bg_area, blend_area, &bg_surface->crop_area);
    printf("[V2D] bg crop area: x=%d, y=%d, w=%d, h=%d\n", bg_surface->crop_area.x, bg_surface->crop_area.y, bg_surface->crop_area.w, bg_surface->crop_area.h);

    //fg
    fg_surface->buf = (void *)fg_buf;
    fg_surface->w = fg_area->x2 - fg_area->x1;
    fg_surface->h = fg_area->y2 - fg_area->y1;

    fg_surface->pitch = lv_gpu_init_pitch(fg_format, fg_surface->w);
    if(0 == fg_surface->pitch) {
        printf("[ERR] unsupported fg format =%d\n", fg_format);
        return -1;
    }

    /* Make fg crop area relative to `fg_area` to simplify draw to `fg_buf`*/
    lv_gpu_get_crop_area(fg_area, blend_area, &fg_surface->crop_area);
    printf("[V2D] fg crop area: x=%d, y=%d, w=%d, h=%d\n", fg_surface->crop_area.x, fg_surface->crop_area.y, fg_surface->crop_area.w, fg_surface->crop_area.h);

    //mask
    /* ignore mask_buf, px_opa from fg's pixel alpha, premult factor from overall opa */
    if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
        //fg_has_alpha, no mask buf
        mask_surface->buf = NULL;


    } else if(LV_DRAW_MASK_RES_CHANGED) {
        /* if has alpha, has transform, BGRA divided to BGA and A8.
         * fg is BGR, all pixel's alpha is divided to mask_buf,
         * after all other masks blended together, output mask is not transport
         */
        //has mask buf, fg no alpha channel.
        mask_surface->buf = (void *)mask_buf;

        mask_surface->w = mask_area->x2 - mask_area->x1;
        mask_surface->h = mask_area->y2 - mask_area->y1;

        mask_surface->pitch = lv_gpu_init_pitch(A8, mask_surface->w);
        if(0 == mask_surface->pitch) {
            printf("[ERR] unsupported mask format =%d\n", bg_format);
            return -1;
        }

        /* Make bg crop area relative to `bg_area` to simplify draw to `bg_buf`*/
        lv_gpu_get_crop_area(mask_area, blend_area, &mask_surface->crop_area);
        printf("[V2D] mask crop area: x=%d, y=%d, w=%d, h=%d\n", mask_surface->crop_area.x, mask_surface->crop_area.y, mask_surface->crop_area.w, mask_surface->crop_area.h);

        out_surface->alpha_selection = ALPHA_FROM_MASK_BUFFER;
    }

    //Since fg/bg and output all need BGR, so no need set rb_swap for fg/bg surface and output surface.
    //bg_surface.rb_swap = 1; /* swap RGB to BGR */
    //fg_surface.rb_swap = 1; /* swap RGB to BGR */

    //out_surface
    /*
     * blend output buf can be same as bg_buf, Since output's format is same as bg's format.
     * eg A8 blend A8 ->A8; BGA map + mask map blend BGA map to BGA map.
     * if output and bg's format are different, v2d must use different buf for output
     */
    out_surface->buf = (void *)bg_buf;

    out_surface->dither = 0; /*only BGR888 to BGR565 need dither enable */

    if(opa == 255) {
        out_surface->premult_enable = 0;
    } else {
        out_surface->premult_enable = 1;
        out_surface->premult_factor = opa;
    }
    out_surface->format = bg_format; //lcd need 565. if mask A8 blend A8. out_surface.format=A8
    /* output blend relative area same as bg crop area */
    memcpy((char *)&out_surface->blend_area, (char *)&bg_surface->crop_area, sizeof(V2D_AREA));

    out_surface->mix_type = MIX_TYPE_2;

    return 0;
}

/*
 * also support A8 blend A8, so bg_buf and fg_buf use uint8_t *
 * fg_buf may has alpha, so fg_buf need uint8_t *
 */
int lv_gpu_v2d_blend(const lv_area_t * bg_area, uint8_t * bg_buf, LV_V2D_IMAGE_FORMAT bg_format,
                     const lv_area_t * blend_area,
                     const lv_area_t * fg_area, const uint8_t * fg_buf, lv_opa_t opa, LV_V2D_IMAGE_FORMAT fg_format,
                     const lv_area_t * mask_area, const lv_opa_t * mask_buf,
                     lv_draw_mask_res_t mask_res,
                     lv_blend_mode_t blend_mode)
{
    /*Do not draw transparent things*/
    if(opa < LV_OPA_MIN) return 0;
    if(mask_res == LV_DRAW_MASK_RES_TRANSP) return 0;

    int ret = 0;
    V2D_BLEND_T v2d_blend_info;
    _lv_memset_00((void *)&v2d_blend_info, sizeof(V2D_BLEND_T));

    printf("lv_gpu_v2d_blend: enter. mask_res=%d, overall opa =%d\n", mask_res, opa);
    printf("[IN: bg_area   ] x1=%d, y1=%d, x2=%d, y2=%d\n", bg_area->x1, bg_area->y1, bg_area->x2, bg_area->y2);
    printf("[IN: fg_area   ] x1=%d, y1=%d, x2=%d, y2=%d\n", fg_area->x1, fg_area->y1, fg_area->x2, fg_area->y2);
    printf("[IN: blend_area] x1=%d, y1=%d, x2=%d, y2=%d\n", blend_area->x1, blend_area->y1, blend_area->x2, blend_area->y2);


    LV_V2D_IMAGE_FORMAT format = RGB565;
#if LV_COLOR_DEPTH == 32
    format = RGB888;
#endif

    if(FORMAT_DEFAULT == bg_format) {
        bg_format = format;
    }

    if(FORMAT_DEFAULT == fg_format) {
        fg_format = format;
    }

    ret = lv_gpu_blend_init(bg_area, bg_buf, bg_format,
                            blend_area,
                            fg_area, fg_buf, opa, fg_format,
                            mask_area, mask_buf,
                            mask_res,
                            blend_mode,
                            &v2d_blend_info);
    if(ret < 0) return -1;

#if !LV_USE_GPU_V2D_SOFTWARE_SIMULATOR
    v2d_blend(&v2d_blend_info);

#else
    /* software simulator v2d */
    /* ignore mask_buf, px_opa from fg's pixel alpha, premult factor from overall opa */
    if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
        lv_gpu_v2d_blend_fg_has_alpha_no_mask(bg_area, (lv_color_t *)bg_buf,
                                              blend_area,
                                              fg_area, fg_buf, opa,
                                              blend_mode);


    } else if(LV_DRAW_MASK_RES_CHANGED) {
        /* if has alpha, has transform, BGRA divided to BGA and A8.
         * fg is BGR, all pixel's alpha is divided to mask_buf,
         * after all other masks blended together, output mask is not transport
         */
        lv_gpu_v2d_blend_fg_no_alpha_has_mask(bg_area, (lv_color_t *)bg_buf,
                                              blend_area,
                                              fg_area, fg_buf, opa,
                                              mask_area, mask_buf,
                                              mask_res,
                                              blend_mode);
    }
#endif

    return 0;
}

/* no overall opa , no mask, blend A8 with A8*/
int lv_gpu_A8_blend(const lv_area_t * bg_area, uint8_t * bg_buf,
                    const lv_area_t * blend_area,
                    const lv_area_t * fg_area, const uint8_t * fg_buf)
{
    return lv_gpu_v2d_blend(bg_area, bg_buf, A8,
                            blend_area,
                            fg_area, fg_buf, 255, A8,
                            NULL, NULL,
                            LV_DRAW_MASK_RES_FULL_COVER,
                            LV_BLEND_MODE_NORMAL);
}

/* crop also supports transfer format */
int lv_gpu_v2d_crop_and_transfer(const lv_area_t * src_area, const uint8_t * src_buf, LV_V2D_IMAGE_FORMAT src_format,
                                 const lv_area_t * dst_area, uint8_t * dst_buf,  LV_V2D_IMAGE_FORMAT dst_format)
{
    (void)src_buf;
    (void)dst_buf;
    (void)src_area;
    (void)dst_area;

    V2D_SRC_SURFACE src_surface;
    V2D_SRC_SURFACE dst_surface;

    _lv_memset_00((void *)&src_surface, sizeof(V2D_SRC_SURFACE));
    _lv_memset_00((void *)&dst_surface, sizeof(V2D_SRC_SURFACE));

    //src
    src_surface.buf = (void *)src_buf;
    src_surface.format = src_format;
    src_surface.w = src_area->x2 - src_area->x1;
    src_surface.h = src_area->y2 - src_area->y1;

    src_surface.pitch = lv_gpu_init_pitch(src_format, src_surface.w);
    if(0 == src_surface.pitch) {
        printf("[ERR] unsupported src format =%d\n", src_format);
        return -1;
    }
    lv_gpu_get_crop_area(src_area, src_area, &src_surface.crop_area);
    printf("[V2D] src crop area: x=%d, y=%d, w=%d, h=%d\n", src_surface.crop_area.x, src_surface.crop_area.y, src_surface.crop_area.w, src_surface.crop_area.h);

    //dst
    dst_surface.buf = dst_buf;
    dst_surface.format = dst_format;
    src_surface.w = src_area->x2 - src_area->x1;
    src_surface.h = src_area->y2 - src_area->y1;

    dst_surface.pitch = lv_gpu_init_pitch(dst_format, dst_surface.w);
    if(0 == dst_surface.pitch) {
        printf("[ERR] unsupported dst format =%d\n", dst_format);
        return -1;
    }

    lv_gpu_get_crop_area(src_area, dst_area, &dst_surface.crop_area);
    printf("[V2D] dst crop area: x=%d, y=%d, w=%d, h=%d\n", dst_surface.crop_area.x, dst_surface.crop_area.y, dst_surface.crop_area.w, dst_surface.crop_area.h);

    v2d_crop_and_transfer(&src_surface, &dst_surface);

    return 0;
}

int lv_gpu_v2d_transfer(const lv_area_t * src_area, const uint8_t * src_buf, LV_V2D_IMAGE_FORMAT src_format,
                        lv_color_t * dst_buf, LV_V2D_IMAGE_FORMAT dst_format)
{
    return lv_gpu_v2d_crop_and_transfer(src_area, src_buf, src_format,
                                        src_area, (uint8_t *)dst_buf, dst_format);
}

/* src_buf has alpha channel, eg BGRA5658, divide alpha to mask_buf, reverse BGR to dst_buf */
int lv_gpu_v2d_divide_alpha_to_mask_A8(const lv_area_t * src_area, const uint8_t * src_buf,
                                       const lv_area_t * dst_area, uint8_t * dst_buf,
                                       lv_opa_t * mask_buf)
{
#if LV_COLOR_DEPTH == 32
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGBA8888, dst_area, dst_buf, RGB888);
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGBA8888, dst_area, (uint8_t *)mask_buf, A8);
#elif LV_COLOR_DEPTH == 16
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGBA5658, dst_area, dst_buf, RGB565);
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGBA5658, dst_area, (uint8_t *)mask_buf, A8);
#else
    printf("[%s] Unsupport format.\n", __func__);
    return -1;
#endif
    return 0;
}

/* src and dst with same default format */
int lv_gpu_v2d_crop(const lv_area_t * src_area, const uint8_t * src_buf,
                    const lv_area_t * dst_area, uint8_t * dst_buf)
{
#if LV_COLOR_DEPTH == 32
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGB888, dst_area, dst_buf, RGB888);
#elif LV_COLOR_DEPTH == 16
    lv_gpu_v2d_crop_and_transfer(src_area, src_buf, RGB565, dst_area, dst_buf, RGB565);
#else
    printf("[%s] Unsupport format.\n", __func__);
    return -1;
#endif
    return 0;
}

int lv_gpu_fill_color(const lv_area_t * bg_area, uint8_t * bg_buf,
                      lv_color_t fg_recolor, lv_opa_t fg_recolor_opa,
                      const lv_area_t * fg_fill_area,
                      uint8_t * mask_buf, lv_draw_mask_res_t mask_res,
                      lv_blend_mode_t blend_mode)
{
    /*Do not draw transparent things*/
    if(fg_recolor_opa < LV_OPA_MIN) return 0;
    if(mask_res == LV_DRAW_MASK_RES_TRANSP) return 0;

    int ret = 0;
    V2D_BLEND_T v2d_blend_info;
    _lv_memset_00((void *)&v2d_blend_info, sizeof(V2D_BLEND_T));

    printf("lv_gpu_fill_color: enter. mask_res=%d, overall opa =%d\n", mask_res, fg_recolor_opa);
    printf("[IN: bg_area   ] x1=%d, y1=%d, x2=%d, y2=%d\n", bg_area->x1, bg_area->y1, bg_area->x2, bg_area->y2);
    printf("[IN: fg_fill_area   ] x1=%d, y1=%d, x2=%d, y2=%d\n", fg_fill_area->x1, fg_fill_area->y1, fg_fill_area->x2, fg_fill_area->y2);

    LV_V2D_IMAGE_FORMAT format = RGB565;
#if LV_COLOR_DEPTH == 32
    format = RGB888;
#endif

    ret = lv_gpu_blend_init(bg_area, bg_buf, format,
                            fg_fill_area,
                            fg_fill_area, NULL, fg_recolor_opa, format,
                            fg_fill_area, mask_buf,
                            mask_res,
                            blend_mode,
                            &v2d_blend_info);
    if(ret < 0) return -1;

    return v2d_fill(&v2d_blend_info, (uint32_t)fg_recolor.full);
}

LV_ATTRIBUTE_FAST_MEM lv_draw_mask_res_t lv_draw_mask_apply_lines(lv_opa_t * mask_buf, lv_coord_t abs_x, lv_coord_t abs_y,
        lv_coord_t len)
{
    bool changed = false;
    lv_draw_mask_common_dsc_t * dsc;

    _lv_draw_mask_saved_t * m = LV_GC_ROOT(_lv_draw_mask_list);

    while(m->param) {
        dsc = m->param;
        lv_draw_mask_res_t res = LV_DRAW_MASK_RES_FULL_COVER;
        res = dsc->cb(mask_buf, abs_x, abs_y, len, (void *)m->param);
        if(res == LV_DRAW_MASK_RES_TRANSP) return LV_DRAW_MASK_RES_TRANSP;
        else if(res == LV_DRAW_MASK_RES_CHANGED) changed = true;

        m++;
    }

    return changed ? LV_DRAW_MASK_RES_CHANGED : LV_DRAW_MASK_RES_FULL_COVER;
}

/* lv draw mask always line by line, there prepare a whole map of mask to blend */
int lv_gpu_all_masks_blend(const lv_area_t * bg_area, uint8_t * mask_buf, lv_draw_mask_res_t * mask_res)
{
    lv_draw_mask_common_dsc_t * dsc;
    _lv_draw_mask_saved_t * m = LV_GC_ROOT(_lv_draw_mask_list);

    while(m->param) {
        dsc = m->param;

        if(LV_DRAW_MASK_TYPE_MAP == dsc->type) {
            lv_draw_mask_map_param_t * p = (lv_draw_mask_map_param_t *)m->param;

            lv_area_t blend_area;
            if(_lv_area_intersect(&blend_area, bg_area, &p->cfg.coords)) {
                /* Got to the current row in the map */
                lv_gpu_A8_blend(bg_area, mask_buf, &blend_area, &p->cfg.coords, (const uint8_t *)p->cfg.map);
                *mask_res = LV_DRAW_MASK_RES_CHANGED;
            }
        } else {
            /* blend line by line */
            int32_t x, y;
            uint32_t px_i = 0;
            lv_coord_t draw_area_h = lv_area_get_height(bg_area);
            lv_coord_t draw_area_w = lv_area_get_width(bg_area);

            for(y = 0; y < draw_area_h; y++) {
                uint32_t px_i_start = px_i;

                for(x = 0; x < draw_area_w; x++, px_i++) {
                    /* blend the masks a line by software */
                    *mask_res = dsc->cb(mask_buf + px_i_start, bg_area->x1, y + bg_area->y1, draw_area_w, (void *)m->param);
                    if(*mask_res == LV_DRAW_MASK_RES_TRANSP) {
                        _lv_memset_00(mask_buf + px_i_start, draw_area_w);
                        *mask_res = LV_DRAW_MASK_RES_CHANGED;
                        return 0;
                    }
                }
            }
        }

        m++;
    }

    return 0;
}

#endif /*LV_USE_GPU_V2D*/
